#include "mmu.h"
#include "i386.h"

#include "debug.h"
#define PRESENTE 1
#define RW 2

/***************************************************
 * Contadores para paginas libres
 * 
 * siempre apuntan a la ultima pagina ocupada
 * los guardamos como uint y a la hora de devolverlos 
 * los convertimos a punteros
 ***************************************************/

uint contador_pagina_kernel = INICIO_PAGINAS_KERNEL-TAMANO_PAGINA;
uint contador_pagina_usuario = INICIO_PAGINAS_USUARIO - TAMANO_PAGINA;

/***************************************************
 * uint pagina_libre_kernel();
 * 
 * Devuelve la proxima pagina libre de kernel
 * Si no hay mas espacio devuelve null
 ***************************************************/
uint* pagina_libre_kernel(){
    //incremento contador
    contador_pagina_kernel = contador_pagina_kernel + TAMANO_PAGINA;
    
    //si no llegue al limite devuelvo la pagina
    if (contador_pagina_kernel < FIN_PAGINAS_KERNEL)
        return (uint *)contador_pagina_kernel;
    else
        return 0;
}

/***************************************************
 * uint pagina_libre_usuario();
 * 
 * Devuelve la proxima pagina libre de usuario
 * Si no hay mas espacio devuelve null
 ***************************************************/
uint* pagina_libre_usuario(){
    //incremento contador
    contador_pagina_usuario = contador_pagina_usuario + TAMANO_PAGINA;
    
    //si no llegue al limite devuelvo la pagina
    if (contador_pagina_usuario < FIN_PAGINAS_USUARIO)
        return (uint *)contador_pagina_usuario;
    else
        return 0;
}

void inicializar_mmu(){
     lcr3((uint) inicializar_dir_usuario());
}


void inicializar_dir_kernel(){
    lcr3((uint) identity_map(4096));
}
uint* inicializar_dir_usuario(){
    return identity_map(2048);
}

/*************************************
 * Crea una estructura de paginacion 
 * con identity map en la cantidad de 
 * Kbytes pasado como parametro
 * 
 * PRE: cant mayor a 0 y menor a 4Mb
 *************************************/
uint* identity_map(uint cant){
    //Pido una pagina para guardar una PD de usuario
    uint *pd = pagina_libre_kernel();
    
    uint i;
    for(i=1;i < 1024;i++){
        // PD entry no presentes R/W
        pd[i] = RW;                        
    }
    
    // Pido una pagina para guardar una PT de usuario
    uint *pt = pagina_libre_kernel();                        
    
    
    // PD entry inicial
    pd[0] = (uint)pt + RW + PRESENTE;                        

    //Identity Map de los primeros 2Mb
    cant = cant / 4;
    uint acum = RW + PRESENTE;
    
    for(i=0; i < cant; i++){
        // PT entry con identity map
        pt[i] = acum;
        acum += TAMANO_PAGINA;
    }
    
    //Vacia las otras entradas de la PT
    for(i = cant; i < 1024; i++){
        pt[i] = 0x0 + RW;    
    }
    
    return pd;
}

void mapear_pagina(uint virtual, uint cr3, uint fisica){
    //obtengo los primeros 10 bits
    uint index = virtual >> 22;
    
    //obtengo la direccion de la PD
    uint *pd = (uint *)(cr3 & ~0xFFF);
    
    //obtengo la direccion de la PT
    uint *pt = (uint *)pd[index];

    //Me fijo si esta presente
    if((uint)pt % 2 == 1){
        //~ logText("PT Presente");
        pt = (uint *)((uint)pt & ~0xFFF);
        
    }else{
        //~ logText("PT NO Presente");        
        
        //Pido una pagina para guardar una PD de usuario
        pt = pagina_libre_kernel();
        
        //~ logText("index en PD");        
        //~ logVal(index, 16);
        //agrego en el PD esta entrada
        pd[index] = (uint)pt + RW + PRESENTE;
        
        //~ logText("PT:");        
        //~ logVal(pd[index], 16);
        uint i;
        //Vacia las entradas de la PT
        for(i=0; i < 1024; i++){
            pt[i] = 0x0 + RW;    
        }

    }
    
    //me quedo con los 10 bits del medio
    index = (virtual << 10) >> 22;

    //Alineo 4K la direccion fisica
    //indico que esta presente
    //e indexo PT
    pt[index] = (fisica & ~0xFFF) + RW + PRESENTE;
    
    //~ logText("PT entry:");        
    //~ logVal(pt[index], 16);
    
    //~ __asm __volatile("xchg %bx, %bx");

        
    //reinicia cache    
}
	
void unmapear_pagina(uint virtual, uint cr3){
    uint *p = buscar_entrada(virtual, cr3);
    
    if((uint)p != 0){
        //borro la entrada
        p[0] = 0x0 + RW;    

        //reinicia cache    
        tlbflush();        
    }
    
}

uint* buscar_entrada(uint virtual, uint cr3){
    //obtengo los primeros 10 bits
    uint index = virtual >> 22;
    
    //obtengo la direccion de la PD
    uint *pd = (uint *)(cr3 & ~0xFFF);
    
    //obtengo la direccion de la PT
    uint *pt = (uint *)pd[index];

    //Si esta presente, borro la entrada
    if((uint)pt % 2 == 1){
        //~ logText("PT Presente");
        pt = (uint *)((uint)pt & ~0xFFF);
        
        //me quedo con los 10 bits del medio
        index = (virtual << 10) >> 22;

        //borro la entrada
        return &(pt[index]);
    }
    
    return (uint *)0;
}


void copiar_pagina(uint source, uint dest){
    //guardo la entrada, si existe, en el cr3 actual
    uint *p_dest = buscar_entrada(dest, rcr3());
    uint dest_antiguo = p_dest[0];
    
    uint *p_src = buscar_entrada(source, rcr3());
    uint src_antiguo = p_src[0];
    
    mapear_pagina(source, rcr3(), source); 
    mapear_pagina(dest, rcr3(), dest); 
    
    uint i=0;
    for(; i < 1024; i++){
        ((uint*)dest)[i]=((uint*)source)[i];
    }
    
    p_src[0] = src_antiguo;
    p_dest[0] = dest_antiguo;
}

